#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <random>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <sstream>

#define C_PI glm::pi<float>()

class CMath {
public:
	static float degrees(float radians) { return glm::degrees(radians); }
	static float radians(float degrees) { return glm::radians(degrees); }

	static float distance(const glm::vec3& p1, const glm::vec3& p2) { return glm::distance(p1, p2); }
	static float distance(const glm::vec2& p1, const glm::vec2& p2) { return glm::distance(p1, p2); }

	/**@return std::max(minVal, std::min(value, maxVal)); */
	static float clamp(float value, float minVal, float maxVal) { return glm::clamp(value, minVal, maxVal); }

	/**@return => x * (1.0 - a) + y * a; */
	static float mix(float x, float y, float a) { return glm::mix(x, y, a); }

	/**@return float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); => t * t * (3.0f - 2.0f * t); */
	static float smoothstep(float edge0, float edge1, float x) { return glm::smoothstep(edge0, edge1, x); }

	/**@brief The random floating point number of [min, max) range can be negative, as long as min <= max */
	static float random(float min = 0.0, float max = 1.0) {
		std::uniform_real_distribution<float> dis(min, max);
		return dis(s_gen);
	}

	/**@brief Random integer of [min, max] range */
	static int randomInt(int min = 0, int max = 100) {
		std::uniform_int_distribution<int> dis(min, max);
		return dis(s_gen);
	}

	static bool randomBool() {
		std::uniform_int_distribution<int> dis(0, 1);
		return dis(s_gen) == 1;
	}

	static float round(float v) { return std::round(v); }
	static float floor(float v) { return std::floor(v); }
	static float ceil(float v) { return std::ceil(v); }
	static float trunc(float v) { return std::trunc(v); }// remove .x: 3.7 -> 3
	static void modf(float v, float* intpart) { std::modf(v, intpart); }

	inline static bool isZeroMat(const glm::mat4& mat4) { return mat4 == glm::mat4(0.0); }
	inline static bool isIdentityMat(const glm::mat4& mat4) { return mat4 == glm::mat4(1.0); }

	static std::string getVec3Str(const glm::vec3& vec3) {
		std::ostringstream oss;
		oss << "(" << vec3.x << "," << vec3.y << "," << vec3.z << ")";
		return oss.str();
	}

	static std::string getMat4Str(const glm::mat4& mat4, int bitw = 8, int precision = 3) {
		std::ostringstream oss;
		oss << std::fixed << std::setprecision(precision);
		const float* p = glm::value_ptr(mat4);
		for (int i = 0; i < 16; ++i) {
			oss << std::setw(bitw) << p[i] << " ";
			if ((i + 1) % 4 == 0) oss << '\n';
		}
		return oss.str();
	}

private:
	static std::random_device s_rd;
	static std::mt19937 s_gen;
};